#include <iostream>
#include <map>
#include <vector>
#include <functional>
#include <readline/readline.h>
#include <readline/history.h>

// This program shows you how to implement a simple command line interface with readline.
// For more information about the readline library, check:
// - https://thoughtbot.com/blog/tab-completion-in-gnu-readline
// - https://www.man7.org/linux/man-pages/man3/readline.3.html
// - https://web.mit.edu/gnu/doc/html/rlman_toc.html

// define a class for supported commands. It has a name and a description
class Command
{
public:
    using function_type = std::function<void()>;
    std::string name;
    std::string description;
    // add a function handler for the command
    function_type handler;
    Command(std::string n, std::string d, function_type h)
        : name(n), description(d), handler(h) {}
};

int show_command_help(int a, int b);

// A map to contain all supported commands
std::map<std::string, Command> commands = {
    {"help", Command("help", "Display this help message", []
                     { show_command_help(0, 0); })},
    {"add", Command("add", "Add a new task", []
                    { std::cout << "Adding a new task\n"; })},
    {"remove", Command("remove", "Remove a task", []
                       { std::cout << "Removing a task\n"; })},
    {"list", Command("list", "List all tasks", []
                     { std::cout << "Listing all tasks\n"; })}};

int show_command_help(int count, int key)
{
    std::cout << "\nAvailable commands:\n";
    for (auto &cmd : commands)
    {
        std::cout << "\t" << cmd.second.name << ": " << cmd.second.description << "\n";
    }
    if (count > 0 || key > 0)
        rl_redraw_prompt_last_line();
    return 0;
}

char *command_completion(const char *text, int state)
{
    static std::vector<std::string> matches;
    const char *buffer = rl_line_buffer;
    // if line buffer contains a space, don't complete
    if (strchr(buffer, ' '))
        return nullptr;
    if (state == 0)
    {
        // find all commands with prefix text
        matches.clear();
        for (auto &cmd : commands)
        {
            if (cmd.first.find(text) == 0)
                matches.push_back(cmd.first);
        }
    }
    return (state < matches.size()) ? strdup(matches[state].c_str()) : nullptr;
}

int main(int, char **)
{
    // setup readline to support command editing
    rl_bind_key('\t', rl_complete);
    // bind ? to show help
    rl_bind_key('?', show_command_help);
    std::string history_file = ".my_history";
    read_history(history_file.c_str());
    rl_completion_suppress_append = 1;
    rl_completion_entry_function = &command_completion;

    while (true)
    {
        // Read user input
        char *input = readline("> ");
        if (input == nullptr)
            break;

        std::string input_str = input;
        // trim whitespace
        input_str.erase(input_str.find_last_not_of(" \n\r\t") + 1);

        // Check if user wants to exit
        if (strcmp(input, "exit") == 0)
        {
            break;
        }

        free(input);
        // Check if command exists
        auto found = commands.find(input_str);
        if (found != commands.end())
        {
            found->second.handler();
            add_history(input);
        }
    }
    write_history(history_file.c_str());

    return 0;
}
